home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 8823 / 8823.xpi / components / omni-search.js
Text File  |  2009-10-19  |  40KB  |  1,077 lines

  1. const Cc = Components.classes;
  2. const Ci = Components.interfaces;
  3. const Cr = Components.results;
  4. const Cu = Components.utils;
  5. const ACR = Ci.nsIAutoCompleteResult;
  6.  
  7. //FF3 only method.
  8. //http://developer.mozilla.org/En/How_to_Build_an_XPCOM_Component_in_Javascript
  9. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  10.  
  11. const log = function(msg) {
  12.   Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage('OM:'+msg);
  13. }
  14.  
  15. function OmnibarSearch() {
  16.   var localeService = Cc["@mozilla.org/intl/nslocaleservice;1"]
  17.                       .getService(Ci.nsILocaleService);
  18.   var stringBundleService = Cc["@mozilla.org/intl/stringbundle;1"]
  19.                             .getService(Ci.nsIStringBundleService);
  20.   this._sb = stringBundleService.createBundle(
  21.               "chrome://omnibar/locale/strings.properties",
  22.               localeService.getApplicationLocale());
  23. };
  24.  
  25. OmnibarSearch.prototype = {
  26.   classDescription: "omnibar search companion",
  27.   classID:          Components.ID("{629F60A2-7C31-11DD-9566-E35956D89593}"),
  28.   contractID:       "@mozilla.org/autocomplete/search;1?name=omnibar-search",
  29.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch]),
  30.   createNewResult: function(searchString) {
  31.     var result = Cc['@mozilla.org/autocomplete/simple-result;1']
  32.                   .createInstance(Ci.nsIAutoCompleteSimpleResult);
  33.     result.setSearchString(searchString);
  34.     result.setDefaultIndex(-1);
  35.     result.setErrorDescription("omnibar search failure");
  36.     result.setSearchResult(Ci.nsIAutoCompleteResult.RESULT_SUCCESS);
  37.     return result;
  38.   },
  39.   startSearch: function(searchString, searchParam, previousResult, listener) {
  40.     searchString = trim(searchString);
  41.     var utils = new SearchUtils();
  42.     var pref = utils._prefBranch;
  43.     // create a simple result object
  44.     var result = this.createNewResult(searchString);
  45.     var kwdInfo = utils.getKeywordInfo(searchString.split(' ')[0]);
  46.     if(kwdInfo) {
  47.       result.appendMatch(searchString,
  48.                  kwdInfo.title + " (keyword: " + kwdInfo.keyword + ")",
  49.                  kwdInfo.iconURL,
  50.                  "bookmark");
  51.       listener.onSearchResult(this, result);
  52.       return;
  53.     }
  54.     
  55.     var showdefaultsearch = false;  //pref.getBoolPref("showdefaultsearch");
  56.     var enabledefaultsearch = pref.getBoolPref("enabledefaultsearch");
  57.     var query = utils.parseQuery(searchString);
  58.     if(searchString.length > 0
  59.        && (showdefaultsearch ||
  60.            // show default search option for query containing @ operator
  61.            (searchString.indexOf("@") >=0 && query[1] && query[1].length > 0) ||
  62.            // and for query starting with search engine keyword
  63.            query[3])) {
  64.       var engines = query[1];
  65.       // no engine option was found
  66.       if(!enabledefaultsearch
  67.          && !(engines && engines.length > 0)
  68.          && !utils.isAProtocolOrLocation(searchString)
  69.         ) {
  70.         // as if user had typed in a valid query with default engine name
  71.         var e = utils._ss.currentEngine;
  72.         query[0] = searchString;
  73.         searchString = pref.getCharPref("defaultqueryformat")
  74.                            .replace("$Q$", searchString).replace("$E$", e.name);
  75.         engines = [e];
  76.       }
  77.       
  78.       if(engines && engines.length > 0) {
  79.         // show default option only if found an engine to search with
  80.         var engineNames = [];
  81.         engines.forEach(function(e) {
  82.           engineNames.push(e.name);
  83.         });
  84.         // XXX when "browser.urlbar.autoFill" is true and this is the first
  85.         // result cursor in textbox with autocomplete goes to the begining after
  86.         // the a space is entered. A workaround is available... it would be to
  87.         // add " " to the searchString, but its better to log a bug for this
  88.         // issue.
  89.         if(utils._mainPref.getBoolPref("browser.urlbar.autoFill")) {
  90.           // XXX this is a temporary workaround for the above mentioned bug.
  91.           // check in next version and remove if its fixed.
  92.           // here we have added an empty space in search suggestion so that even
  93.           // if its the first choice in location-bar autocomplete it wont
  94.           // trigger the buggy behavior.
  95.           searchString = searchString + "";
  96.         }
  97.         var currQry = this.getQueryForEngine(searchString,
  98.                                              query[0],
  99.                                              engineNames.join(", "),
  100.                                              engines.length === 1 ? engines[0].iconURI.spec : undefined);
  101.         // call append match method to add a search result to urlbar autocomplete.
  102.         //
  103.         // result.appendMatch  takes the following arguments:
  104.         //    1. the actual string to be used in the urlbar.
  105.         //    2. comment to be shown for the string in the urlbar.
  106.         //    3. path to the image to be shown besides the comment.
  107.         //    4. some style. dont remember what's it used for... :|
  108.         result.appendMatch.apply(result, currQry);
  109.       }
  110.     }
  111.     listener.onSearchResult(this, result);
  112.   },
  113.   getQueryForEngine: function(query, searchString, engineName, icon) {
  114.     if(trim(searchString).length == 0) {
  115.       searchString = this._sb ?
  116.                       this._sb.GetStringFromName("EmptyStringFiller") : "___";
  117.     }
  118.     // XXX cache these string bundles
  119.     var comment = "";
  120.     try{
  121.     comment = this._sb ? this._sb.GetStringFromName("DefaultSearchCommentFormat") :
  122.                             "search $1 for: $2"
  123.     }catch(e) {
  124.       // failing for swedish locale check to see if there's a problem with file 
  125.       comment = "$1: $2"
  126.     }
  127.     return [
  128.             query,
  129.             comment.replace("$1", engineName).replace("$2", searchString),
  130.             icon || "chrome://omnibar/skin/classic/magnifier.png",
  131.             "omnibar-search"
  132.            ];
  133.   },
  134.   stopSearch: function() {
  135.     // for now nothing to do. everything is synchronous
  136.   }
  137. }
  138.  
  139. /**
  140.  * another component class to enable detailed search which are shown at the end
  141.  * of the normal places results.
  142.  */
  143. DetailedOmnibarSearch = function() {
  144.   OmnibarSearch.call(this);
  145.   this.init();
  146. }
  147.  
  148. DetailedOmnibarSearch.prototype = {
  149.   classDescription: "Firefox Search component for location bar",
  150.   classID:          Components.ID("{AA5CDC32-8148-11DD-99E5-B6AA56D89593}"),
  151.   contractID:       "@mozilla.org/autocomplete/search;1?name=omnibar-search-suggestions",
  152.   __proto__: OmnibarSearch.prototype,
  153.   
  154.   init: function() {
  155.     this._autoComplete = Cc["@mozilla.org/autocomplete/search;1?name=search-autocomplete"]
  156.                                 .createInstance(Ci.nsIAutoCompleteSearch);
  157.     this._utils = new SearchUtils();
  158.   },
  159.   boot: function() {
  160.     // do any init here if required.
  161.     var prefBranch = Cc["@mozilla.org/preferences-service;1"]
  162.                         .getService(Ci.nsIPrefBranch);
  163.     var utils = this._utils;
  164.     var engine = utils._ss.currentEngine;
  165.     this._suggestEnabled = prefBranch.getBoolPref("browser.search.suggest.enabled") &&
  166.                            engine.supportsResponseType("application/x-suggestions+json");
  167.     this._mainPref = Cc["@mozilla.org/preferences-service;1"]
  168.                       .getService(Ci.nsIPrefBranch);
  169.   },
  170.   startSearch: function(searchString, searchParam, previousResult, listener) {
  171.     // before going ahead stop old searches if there are any...
  172.     this.stopSearch();
  173.     var self = this;
  174.     self.boot();
  175.     this._listener = listener;
  176.     this._searchString = searchString;
  177.     var utils = this._utils;
  178.     var query = self._query = utils.parseQuery(searchString);
  179.     var result = this._result = this.createNewResult(searchString);
  180.     // load the preference baranch. will use it for a bunch of operations.
  181.     var prefs = utils._prefBranch;
  182.     var kwdInfo = utils.getKeywordInfo(searchString.split(' ')[0]);
  183.     var fileCompletions = getFileCompletions(searchString);
  184.     if(fileCompletions.length > 0) {
  185.       fileCompletions.forEach(function(f) {
  186.         result.appendMatch(f.path,
  187.                            f.name,
  188.                            "chrome://omnibar/skin/classic/local.png",
  189.                            "omnibar-filepath");
  190.       });
  191.       listener.onSearchResult(this, result);
  192.       return;
  193.     } else if(self._suggestEnabled && (prefs.getBoolPref('enabledefaultsearch') || query[3] || query[4])) {
  194.       self._autoComplete.startSearch(query[0] || searchString, searchParam,
  195.                                      null, new SearchObserver({
  196.                                       onSearchResult: function(search, result) {
  197.                                         self.onSuggestedResult(search, result);
  198.                                       }
  199.                                     }));
  200.     } else {
  201.       this.sendCancelledSearchResult(listener, result);
  202.     }
  203.   },
  204.   sendCancelledSearchResult: function(listener, result) {
  205.     try{
  206.     var self = this;
  207.     //RESULT_NOMATCH_ONGOING,RESULT_FAILURE,RESULT_NOMATCH,RESULT_SUCCESS_ONGOING
  208.     Cc["@mozilla.org/thread-manager;1"]
  209.     .getService(Ci.nsIThreadManager).mainThread.dispatch({
  210.       run: function() {
  211.         try{
  212.         if(self._utils._prefBranch.getCharPref("popupstyle") == 'SIMPLE') {
  213.           // workarounds for different behavior in different popups :|
  214.           result.setSearchResult(Ci.nsIAutoCompleteResult.RESULT_NOMATCH);
  215.         } else {
  216.           result.setSearchResult(Ci.nsIAutoCompleteResult.RESULT_FAILURE);
  217.         }
  218.         listener.onSearchResult(self, result);
  219.         }catch(e){log(e)}
  220.       }
  221.     }, Ci.nsIThread.DISPATH_NORMAL);
  222.     }catch(e){log(e)}
  223.   },
  224.   stopSearch: function() {
  225.     this._autoComplete.stopSearch();
  226.   },
  227.   /**
  228.    * method called after the suggested search results have been found.
  229.    */
  230.   onSuggestedResult: function(search, suggested_result) {
  231.     
  232.     var result = this._result;
  233.     var utils = this._utils;
  234.     var searchString = this._searchString;
  235.     var query = this._query;
  236.     var listener = this._listener;
  237.     var prefs = utils._prefBranch;
  238.     var format = query[2] || prefs.getCharPref("defaultqueryformat");
  239.     var defaultEngines = query[1];
  240.     var $E$, $Q$;
  241.     if(!(defaultEngines && defaultEngines.length > 0)) {
  242.       defaultEngines = [utils._ss.currentEngine];
  243.     }
  244.     // temporary variable used at places to get engine names from engines list.
  245.     var engine_names = [];
  246.     defaultEngines.forEach(function(e) {
  247.       engine_names.push(e.name);
  248.     });
  249.     $E$ = engine_names.join(",");
  250.     var MAX_COUNT = prefs.getIntPref("numsuggestions");
  251.     // for some reason suggested_result is null in a few cases!
  252.     var count = Math.min(MAX_COUNT,
  253.                          suggested_result ? suggested_result.matchCount : 0);
  254.     var old_$Q$;
  255.     var iconURI = defaultEngines.length === 1 ?
  256.                   defaultEngines[0].iconURI.spec :
  257.                   // TODO replace with an icon that shows that its a suggestion
  258.                   "chrome://omnibar/skin/classic/magnifier.png";
  259.     //var results = [], comments = [], styles = [], images = [];
  260.     for(var i = 0; i < count; i++) {
  261.       //    1. the actual string to be used in the urlbar.
  262.       //    2. comment to be shown for the string in the urlbar.
  263.       //    3. path to the image to be shown besides the comment.
  264.       //    4. some style. dont remember what's it used for... :|
  265.       $Q$ = suggested_result.getValueAt(i);
  266.       // TODO verify the correctness of this logic
  267.       var possible_url = $Q$.replace(/^http(s)\s/, "http://")
  268.                             .replace(/\s/g, ".");
  269.       // XXX for addresses starting with "http ", it should be replaced with
  270.       // "http://"
  271.       if(($Q$.indexOf("www") === 0 || $Q$.indexOf("http") === 0) && utils.isAProtocolOrLocation(possible_url)) {
  272.         $Q$ = possible_url;
  273.       }
  274.       if( $Q$ !== old_$Q$) {
  275.         var comment;
  276.         if(utils.isAProtocolOrLocation($Q$)) {
  277.           // TODO cache these string bundles
  278.           comment = this._sb ? this._sb.GetStringFromName("UrlSuggestCommentFormat") :
  279.                                "suggested url: $1"
  280.           result.appendMatch($Q$,
  281.                              comment.replace("$1", $Q$),
  282.                              utils.getIconSpec($Q$),
  283.                              "omnibar-suggestion-url");
  284.         } else {
  285.           comment = this._sb ? this._sb.GetStringFromName("PhraseSuggestCommentFormat") :
  286.                                "search $1 for suggestion: $2"
  287.           result.appendMatch(trim(format.replace("$Q$", $Q$).replace("$E$", $E$)),
  288.                              comment.replace("$1", $E$).replace("$2", $Q$),
  289.                              iconURI,
  290.                              "omnibar-suggestion-phrase");
  291.         }
  292.       }
  293.       old_$Q$ = $Q$;
  294.     }
  295.     
  296.     // now append other results...
  297.     var $Q$ = query[0] || searchString;
  298.     var engineseparator = prefs.getCharPref("engineseparator");
  299.     
  300.     listener.onSearchResult(this, result);
  301.   }
  302. }
  303.  
  304. // our implementation of nsIAutoCompleteObserver
  305. SearchObserver = function(owner) {
  306.   this.owner = owner;
  307. }
  308.  
  309. SearchObserver.prototype = {
  310.   QueryInterface: function(iid) {
  311.     if(iid === Ci.nsIAutoCompleteObserver
  312.        || iid === Ci.nsISupports) {
  313.       return this;
  314.     }
  315.     throw Components.results.NS_ERROR_NO_INTERFACE;
  316.   },
  317.   onSearchResult: function(search, result) {
  318.     this.owner.onSearchResult(search, result);
  319.   }
  320. }
  321.  
  322. // omnibar-allinone
  323. OmnibarAllInOne = function() {
  324.   OmnibarSearch.call(this);
  325.   this.init();
  326. }
  327.  
  328. OmnibarAllInOne.prototype = {
  329.   classDescription: "Firefox Search and History component for location bar",
  330.   classID:          Components.ID("{4087d5ad-ab64-4314-8899-fb9ccd7afe41}"),
  331.   contractID:       "@mozilla.org/autocomplete/search;1?name=omnibar-allinone",
  332.   __proto__: OmnibarSearch.prototype,
  333.   
  334.   init: function() {
  335.     // do any init here if required.
  336.     this.mainPrefs = Cc["@mozilla.org/preferences-service;1"]
  337.                       .getService(Ci.nsIPrefBranch);
  338.     this.prefs = Cc["@mozilla.org/preferences-service;1"]
  339.                         .getService(Ci.nsIPrefService)
  340.                         .getBranch("extensions.omnibar.");
  341.     this.utils = new SearchUtils();
  342.     this.omnibarSearch = new DetailedOmnibarSearch();
  343.     this.historySearch = Cc["@mozilla.org/autocomplete/search;1?name=history"].createInstance(Ci.nsIAutoCompleteSearch);
  344.   },
  345.   query: null,
  346.   listener: null,
  347.   searchString: null,
  348.   historySearchOn: false,
  349.   omnibarSearchOn: false,
  350.   startSearch: function(searchString, searchParam, previousResult, listener) {
  351.     var utils = this.utils;
  352.     var self = this;
  353.     this.listener = listener;
  354.     this.searchString = searchString;
  355.     // query === [search_str, engines, user_format, isKeyword, hasOperator]
  356.     var query = this.query = utils.parseQuery(searchString);
  357.     var resultDisplayStrategy = "HISTORY";
  358.     if(query.length > 0 && (query[3] || query[4])) {
  359.       resultDisplayStrategy = "OMNIBAR";
  360.     } else if(trim(searchString).indexOf(" ") > 0) {
  361.       resultDisplayStrategy = "OMNIBAR+HISTORY";
  362.     }
  363.     var result = this.result = new CompositeAutoCompleteResult(searchString, resultDisplayStrategy);
  364.     if(query.length == 0 || (!query[3] && !query[4])) {
  365.       // perform a history search only when a search engine keyword is not used
  366.       // and search engine operator is not used
  367.       historySearchOn = true;
  368.       this.historySearch.startSearch(searchString, searchParam,
  369.                                      previousResult, new SearchObserver({
  370.                                       onSearchResult: function(search, result) {
  371.                                         self.onHistoryResult(search, result);
  372.                                       }
  373.                                     }));
  374.     }
  375.     if(!query[3] && !query[4] && !this.prefs.getBoolPref('enabledefaultsearch')) return;
  376.     
  377.     this.omnibarSearchOn = true;
  378.     this.omnibarSearch.startSearch(searchString, searchParam,
  379.                                    previousResult, new SearchObserver({
  380.                                     onSearchResult: function(search, result) {
  381.                                       self.onOmnibarResult(search, result);
  382.                                     }
  383.                                   }));
  384.   },
  385.   onHistoryResult: function(search, history_result) {
  386.     var result = this.result;
  387.     result.setHistoryResult(history_result);
  388.     this.listener.onSearchResult(this, result);
  389.   },
  390.   onOmnibarResult: function(search, omnibar_result) {
  391.     this.omnibarSearchOn = false;
  392.     var result = this.result;
  393.     result.setOmnibarResult(omnibar_result);
  394.     this.listener.onSearchResult(this, result);
  395.   },
  396.   stopSearch: function() {
  397.     this.historySearch.stopSearch();
  398.     this.omnibarSearch.stopSearch();
  399.   }
  400. }
  401.  
  402. var MAX = 20;
  403. var OMNIBAR = 'O', HISTORY = 'H';
  404. const DISTRIBUTION_STRATEGIES = {
  405.   "OMNIBAR": [[OMNIBAR, MAX]],
  406.   "OMNIBAR+HISTORY": [[OMNIBAR, 2], [HISTORY, 4], [OMNIBAR, 4], [HISTORY, MAX]],
  407.   "HISTORY": [[HISTORY, MAX], [OMNIBAR, MAX]]
  408. };
  409.  
  410. (function(){
  411. for(let name in DISTRIBUTION_STRATEGIES) {
  412.   var array = [], strategy = DISTRIBUTION_STRATEGIES[name];
  413.   for(let i = 0, len = strategy.length; i < len; i += 1) {
  414.     var sourceAndBlock = strategy[i];
  415.     var source = sourceAndBlock[0];
  416.     for(let i = 0, len = sourceAndBlock[1]; i < len; i +=1) {
  417.       array.push(source);
  418.     }
  419.   }
  420.   DISTRIBUTION_STRATEGIES[name] = array;
  421. }
  422. })();
  423.  
  424. // Implements nsIAutoCompleteResult
  425. function SimpleAutoCompleteResult(searchString, searchResult,
  426.                                   defaultIndex, errorDescription,
  427.                                   results, comments, styles, images) {
  428.   this.searchString = searchString;
  429.   this.searchResult = searchResult;
  430.   this.defaultIndex = defaultIndex;
  431.   this.errorDescription = errorDescription;
  432.   this.results = results;
  433.   this.comments = comments;
  434.   this.styles = styles;
  435.   this.images = images;
  436. }
  437.  
  438. SimpleAutoCompleteResult.prototype = {
  439.   searchString: "",
  440.   searchResult: 0,
  441.   defaultIndex: 0,
  442.   errorDescription: "",
  443.   results: [],
  444.   comments: [],
  445.   styles: [],
  446.   images: [],
  447.  
  448.   get matchCount() {
  449.     return this.results.length;
  450.   },
  451.   getValueAt: function(index) {
  452.     return this.results[index];
  453.   },
  454.   getCommentAt: function(index) {
  455.     return this.comments[index];
  456.   },
  457.   getStyleAt: function(index) {
  458.     return this.styles[index];
  459.   },
  460.   getImageAt : function (index) {
  461.     return this.images[index];
  462.   },
  463.   removeValueAt: function(index, removeFromDb) {
  464.     this.results.splice(index, 1);
  465.     this.comments.splice(index, 1);
  466.     this.styles.splice(index, 1);
  467.     this.images.splice(index, 1);
  468.   },
  469.   QueryInterface: function(aIID) {
  470.     if (!aIID.equals(Ci.nsIAutoCompleteResult) && !aIID.equals(Ci.nsISupports))
  471.         throw Components.results.NS_ERROR_NO_INTERFACE;
  472.     return this;
  473.   }
  474. };
  475.  
  476. // nsIAutoCompleteResult
  477. function CompositeAutoCompleteResult(searchString, strategy) {
  478.   this.searchString = searchString;
  479.   this.strategy = strategy = strategy ||  "HISTORY";
  480.   //this.has_history = strategy.indexOf('HISTORY') > 0;
  481.   //this.has_omnibar = strategy.indexOf('OMNIBAR') > 0;
  482. }
  483.  
  484. CompositeAutoCompleteResult.prototype = {
  485.   strategy: "NORMAL",
  486.   searchString: "",
  487.   get searchResult() {
  488.     var omnibarResult = this.omnibarResult;
  489.     var historyResult = this.historyResult;
  490.     if(this.omnibarResult && this.omnibarResult.searchResult == ACR.RESULT_SUCCESS_ONGOING) {
  491.       return ACR.RESULT_SUCCESS_ONGOING;
  492.     }
  493.     if(this.historyResult && this.historyResult.searchResult == ACR.RESULT_SUCCESS_ONGOING) {
  494.       return this.historyResult.searchResult;
  495.     }
  496.     // TODO it would be good to inspect the result to handle failures
  497.     return ACR.RESULT_SUCCESS;
  498.   },
  499.   defaultIndex: 0,
  500.   _errorDescription: null,
  501.   displayTemplateItems: [],
  502.   get errorDescription() {
  503.     return this._errorDescription || (this.historyResult ? this.historyResult.errorDescription : "<Not Available>");
  504.   },
  505.   omnibarResult: null,
  506.   historyResult: null,
  507.   setOmnibarResult: function(result) {
  508.     this.omnibarResult = result;
  509.     this._update();
  510.   },
  511.   setHistoryResult: function(result) {
  512.     this.historyResult = result;
  513.     this._update();
  514.   },
  515.   _update: function() {
  516.     var displayTemplateItems = DISTRIBUTION_STRATEGIES[this.strategy].slice();
  517.     var omnibar_count = 0;
  518.     var history_count = 0;
  519.     var omnibar_max = this.omnibarResult ? this.omnibarResult.matchCount : 0;
  520.     var history_max = this.historyResult ? this.historyResult.matchCount : 0;
  521.     var max = Math.min(MAX, omnibar_max + history_max);
  522.     this._matchCount = max;
  523.     
  524.     for(var i = 0; i < max; i += 1) {
  525.       if(omnibar_max <= omnibar_count) {
  526.         displayTemplateItems.splice(i);
  527.         for(var j = i; j < max; j += 1) {
  528.           displayTemplateItems.push([HISTORY, history_count]);
  529.           history_count += 1;
  530.         }
  531.         break;
  532.       } else if(history_max <= history_count) {
  533.         displayTemplateItems.splice(i);
  534.         for(var j = i; j < max; j += 1) {
  535.           displayTemplateItems.push([OMNIBAR, omnibar_count]);
  536.           omnibar_count += 1;
  537.         }
  538.         break;
  539.       }
  540.       var src = displayTemplateItems[i];
  541.       if(src == OMNIBAR) {
  542.         displayTemplateItems[i] = [OMNIBAR, omnibar_count];
  543.         omnibar_count += 1;
  544.       } else {
  545.         displayTemplateItems[i] = [HISTORY, history_count];
  546.         history_count += 1;
  547.       }
  548.     }
  549.     this.displayTemplateItems = displayTemplateItems;
  550.   },
  551.   get matchCount() {
  552.     return this._matchCount;
  553.   },
  554.   getValueAt: function(index) {
  555.     var [type, i] = this.displayTemplateItems[index];
  556.     return this[type == OMNIBAR ? 'omnibarResult' : 'historyResult'].getValueAt(i);
  557.   },
  558.   getCommentAt: function(index) {
  559.     var [type, i] = this.displayTemplateItems[index];
  560.     return this[type == OMNIBAR ? 'omnibarResult' : 'historyResult'].getCommentAt(i);
  561.   },
  562.   getStyleAt: function(index) {
  563.     var [type, i] = this.displayTemplateItems[index];
  564.     return this[type == OMNIBAR ? 'omnibarResult' : 'historyResult'].getStyleAt(i);
  565.   },
  566.   getImageAt: function (index) {
  567.     var [type, i] = this.displayTemplateItems[index];
  568.     return this[type == OMNIBAR ? 'omnibarResult' : 'historyResult'].getImageAt(i);
  569.   },
  570.   removeValueAt: function(index, removeFromDb) {
  571.     // NOT implemented
  572.   },
  573.   QueryInterface: function(aIID) {
  574.     if (!aIID.equals(Ci.nsIAutoCompleteResult) && !aIID.equals(Ci.nsISupports))
  575.         throw Components.results.NS_ERROR_NO_INTERFACE;
  576.     return this;
  577.   }
  578. };
  579.  
  580.  
  581.  
  582. // list of *all* available tlds. update whenever a new tld pops-up.
  583. const TLDS = ["aero","asia","biz","cat","com","coop","edu","gov","info","int",
  584.               "jobs","mil","mobi","museum","name","net","org","pro","tel",
  585.               "travel","ac","ad","ae","af","ag","ai","al","am","an","ao","aq",
  586.               "ar","as","at","au","aw","ax","az","ba","bb","bd","be","bf","bg",
  587.               "bh","bi","bj","bm","bn","bo","br","bs","bt","bv","bw","by","bz",
  588.               "ca","cc","cd","cf","cg","ch","ci","ck","cl","cm","cn","co","cr",
  589.               "cu","cv","cx","cy","cz","de","dj","dk","dm","do","dz","ec","ee",
  590.               "eg","er","es","et","eu","fi","fj","fk","fm","fo","fr","ga","gb",
  591.               "gd","ge","gf","gg","gh","gi","gl","gm","gn","gp","gq","gr","gs",
  592.               "gt","gu","gw","gy","hk","hm","hn","hr","ht","hu","id","ie","il",
  593.               "im","in","io","iq","ir","is","it","je","jm","jo","jp","ke","kg",
  594.               "kh","ki","km","kn","kp","kr","kw","ky","kz","la","lb","lc","li",
  595.               "lk","lr","ls","lt","lu","lv","ly","ma","mc","md","me","mg","mh",
  596.               "mk","ml","mm","mn","mo","mp","mq","mr","ms","mt","mu","mv","mw",
  597.               "mx","my","mz","na","nc","ne","nf","ng","ni","nl","no","np","nr",
  598.               "nu","nz","om","pa","pe","pf","pg","ph","pk","pl","pm","pn","pr",
  599.               "ps","pt","pw","py","qa","re","ro","rs","ru","rw","sa","sb","sc",
  600.               "sd","se","sg","sh","si","sj","sk","sl","sm","sn","so","sr","st",
  601.               "su","sv","sy","sz","tc","td","tf","tg","th","tj","tk","tl","tm",
  602.               "tn","to","tp","tr","tt","tv","tw","tz","ua","ug","uk","us","uy",
  603.               "uz","va","vc","ve","vg","vi","vn","vu","wf","ws","ye","yt","yu",
  604.               "za","zm","zw"];
  605.  
  606.  
  607. var SearchUtils = function() {
  608.   this.wrappedJSObject = this;
  609.   this.init();
  610. }
  611.  
  612. SearchUtils.prototype = {
  613.   classDescription: "Omnibar search query parser",
  614.   classID:          Components.ID("{0cca1b29-1489-4826-ba0c-21fee771afbd}"),
  615.   contractID:       "@ajitk.com/omnibar/queryparser;1",
  616.   QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports]),
  617.   RE_PROTOCOL_PREFIX: /^(www\.|http:|htttps:|ftp:|file:|chrome:)/i,
  618.   RE_IP: /^(\d{1,3}\.){3}\d{1,3}$/,
  619.   init: function() {
  620.     var ss = this._ss = Cc['@mozilla.org/browser/search-service;1']
  621.                 .getService(Ci.nsIBrowserSearchService);
  622.     var engines = this._engines = [];
  623.     ss.getEngines({}, []).forEach(function(e) {
  624.       if(e.hidden !== true) {
  625.         engines.push(e);
  626.       }
  627.     });
  628.     this._prefBranch = Cc["@mozilla.org/preferences-service;1"]
  629.                         .getService(Ci.nsIPrefService)
  630.                         .getBranch("extensions.omnibar.");
  631.     this._mainPref = Cc["@mozilla.org/preferences-service;1"]
  632.                         .getService(Ci.nsIPrefBranch);
  633.     this._bookmarks = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
  634.                       getService(Ci.nsINavBookmarksService);
  635.     this._faviconService = Cc["@mozilla.org/browser/favicon-service;1"]
  636.                          .getService(Ci.nsIFaviconService);
  637.   },
  638.   getAllEngines: function() {
  639.     return this._engines;
  640.   },
  641.   getIconSpec: function(uri){
  642.     try {
  643.       if(typeof uri === "string") {
  644.         // TODO create a URL object.
  645.         return "";
  646.       }
  647.       var iconURI = this._faviconService.getFaviconForPage(uri);
  648.       return iconURI ? iconURI.spec : "";
  649.     } catch (e){}
  650.     return "";
  651.   },
  652.   getKeywordInfo: function(keyword) {
  653.     // currently in FF, keywords cannot be a capital letter. when
  654.     // getURIForKeyword is called for a keyword in uppercase, it hangs FF. fix
  655.     // for now is to convert the keyword to lower case.
  656.     keyword = keyword.toLowerCase();
  657.     try {
  658.       var bms = this._bookmarks;
  659.       // getting some errors a this line
  660.       var kwdURI = bms.getURIForKeyword(keyword);
  661.       if (kwdURI) {
  662.         var title = "keyword " + keyword;
  663.         var iconURL = this.getIconSpec(kwdURI);
  664.         var items = bms.getBookmarkIdsForURI(kwdURI, {});
  665.         for(var i = 0; items.length; i++) {
  666.           if(bms.getKeywordForBookmark(items[i]) == keyword) {
  667.             title = bms.getItemTitle(items[i]);
  668.             break;
  669.           }
  670.         }
  671.         return {
  672.           keyword: keyword,
  673.           spec: kwdURI.spec,
  674.           title: title,
  675.           iconURL: iconURL
  676.         };
  677.       }
  678.     } catch(e){}
  679.     return undefined;
  680.   },
  681.   parseQuery: function(query) {
  682.     // there a few things that we need to keep in mind here.
  683.     // we need to respect user's way of searching things and should help him
  684.     // search efficiently. a typical search process would consist of two things:
  685.     // 1. the search query
  686.     // 2. intended search engine(s). this is a tricky one to identify. our goal
  687.     //    is to identify the search engines using @ operatoror or usage of
  688.     //    search engine keywords as identifier of search engine. So, this is
  689.     //    what we are going to do to parse user queries: first look for
  690.     //    operator "@" at the begining of the search string.
  691.     
  692.     //    Case 1. if found "@" look for a syntax "@engine1,engine2, engine3
  693.     //    search query" notice the empty space behind engine3 keyword. keep
  694.     //    looking for comma-separated search eingine and stop as soon an entry
  695.     //    is found that does not stand for a search engine.
  696.     
  697.     //    Case 2. user has entered a normal string (not starting with "@"). Now
  698.     //    there can be a possibility that the user is trying to use a search
  699.     //    engine keyword. In this case, find the keyword string (test words
  700.     //    separated by space if it is a keyword) and perform search accordingly.
  701.     
  702.     //    in both cases it is important to show the intended result of user's
  703.     //    query. user can choose to learn and refine his query so one can search
  704.     //    easily and more intuitively.
  705.     
  706.     //    Case 3. "@engine name one, engine name two search query" How to handle
  707.     //    this case where the engine name contains spaces and they may not be
  708.     //    separated from teh search query with comma? TODO find a good solution
  709.     var search_str = "";
  710.     var engines = [];
  711.     var isKeyword = false;
  712.     var hasOperator = true;
  713.     var pref = this._prefBranch;
  714.     var OP = pref.getCharPref('operator');
  715.     var SEP = pref.getCharPref('engineseparator');
  716.     var user_format = OP + "$E$ $Q$";
  717.     
  718.     query = trim(query);
  719.     
  720.     // check if the query starts with a standard protocol.
  721.     if(this.isAProtocolOrLocation(query)) {
  722.       return [];
  723.     }
  724.  
  725.     // no protocol is being used. proceed ahead with parsing the query.
  726.     // before going ahead, there's one more kind of url that we need to handle.
  727.     // what if the user is typing in some intranet url such as http://home/
  728.     // etc? The best way to test that will be to actually try that and find out
  729.     // if that kind of url is active and can be used or not.
  730.     var enabledefaultsearch = pref.getBoolPref("enabledefaultsearch");
  731.     var idxOfAt = query.indexOf(OP);
  732.     if( idxOfAt === 0) {
  733.       var keys = [];
  734.       // what we are trying to parse: @ engine1 , engine2 ,engine3 search string
  735.       search_str = trim(query.substring(1));
  736.       // search_str = engine1 , engine2 ,engine3 search string
  737.       var sequence = search_str.split(SEP);
  738.       // sequence = ["engine1 ", " engine2 ", "engine3 search string"]
  739.       var last_str = trim(sequence.pop());
  740.       // sequence = ["engine1 ", " engine2 "]
  741.       var end_sequence = last_str.split(" ");
  742.       // end_sequence = ["engine3", "search", "string"]
  743.       sequence.push(end_sequence.shift());
  744.       // sequence = ["engine1 ", " engine2 ", "engine3"]
  745.       // end_sequence = ["search", "string"]
  746.       while(sequence.length >= 0) {
  747.         var next_name = sequence[0];
  748.         var finds = this.findEngines([next_name]);
  749.         //log("!next engine name:|" + next_name + "| has N: " + finds.length);
  750.         if(finds.length > 0) {
  751.           keys.push(next_name);
  752.           engines = engines.concat(finds);
  753.           sequence.shift();
  754.         } else if(next_name && next_name.length == 0) {
  755.           // search engine names were separated by more than one whitespaces
  756.           sequence.shift();
  757.         } else {
  758.           // found an entry which is not a valid search engine. time to stop;
  759.           break;
  760.         }
  761.       }
  762.       search_str = trim(sequence.join(SEP) + " " + end_sequence.join(" "));
  763.       user_format = [OP, keys.join(SEP), " $Q$"].join("");
  764.     } else if(idxOfAt > 0) {
  765.       // once we know that there is an "@" character, reset the query to assume
  766.       // it to be at the end. there maybe a "@"(OP) character in search string
  767.       idxOfAt = query.lastIndexOf(OP);
  768.       user_format = "$Q$ "+OP+"$E$";
  769.       search_str = query.substring(0, idxOfAt);
  770.       var engines_str = query.substring(idxOfAt + 1);
  771.       engines = this.findEngines(engines_str.split(SEP));
  772.     } else {
  773.       // it is also possible to perform search in the form of: g y x search
  774.       // query as one of the users suggested! this method can be refoactored to
  775.       // reuse the engine parsing logic if there is a need to!
  776.       hasOperator = false;
  777.       // look for any search engine keyword
  778.       var seq = query.split(" ");
  779.       var key = seq.shift();
  780.       var engine = this.findByKeyword(key);
  781.       if(engine) {  // user is going to use engine by keyword
  782.         isKeyword = true;
  783.         user_format = [key, "$Q$"].join(" ");
  784.         engines.push(engine);
  785.         search_str = seq.join(" ");
  786.       } else if(enabledefaultsearch) {
  787.         search_str = query;
  788.       }
  789.     }
  790.     if(engines.length === 0) {
  791.       hasOperator = false;
  792.       // if none of the engines were found to match, search using default engine
  793.       if(enabledefaultsearch) {
  794.         engines.push(this._ss.currentEngine);
  795.       }
  796.     }
  797.     if(enabledefaultsearch && engines.length == 1 && engines[0] == this._ss.currentEngine) {
  798.       user_format = "$Q$";
  799.     }
  800.     //log([search_str, engines, user_format, isKeyword, hasOperator])
  801.     return [search_str, engines, user_format, isKeyword, hasOperator];
  802.   },
  803.   isAProtocolOrLocation: function(query) {
  804.     // first check if the query starts with a standard protocol.
  805.     if(this.RE_PROTOCOL_PREFIX.test(query) || this.RE_IP.test(query)) {
  806.       return true;
  807.     }
  808.     if(query.indexOf(" ") < 0) {
  809.       if(query.indexOf("/") > 0) {
  810.         return true;
  811.       }
  812.       if(this.getKeywordInfo(query) != null) {
  813.         return true;
  814.       }
  815.       
  816.       var lastIndexOfDot = query.lastIndexOf(".");
  817.       if(lastIndexOfDot > 0) {
  818.         // check for a possible TLD
  819.         var tld_name = query.substring(lastIndexOfDot + 1);
  820.         if(TLDS.indexOf(tld_name) >= 0) { // found a valid TLD!
  821.           return true;
  822.         }
  823.       }
  824.     }
  825.     
  826.     // perform a generic test if a protocol is being used.
  827.     var protocol_name = query.substring(0, query.indexOf(":"));
  828.     if(protocol_name.length > 0) { //a probable candidate!
  829.       try {
  830.         if(protocol_name.indexOf(" ") < 0
  831.            && Cc["@mozilla.org/network/protocol;1?name="+protocol_name]
  832.            ) {
  833.           return true;
  834.         }
  835.       } catch (e) {}
  836.     }
  837.     
  838.     // final test for a posible fiel path
  839.     try {
  840.       // this is a bad check. on some systems, it may not work even for a valid
  841.       // path
  842.       var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
  843.       file.initWithPath(query);
  844.       // came here w/o any exception => a valid file path
  845.       return true;
  846.     } catch(e){
  847.     }
  848.     // another check for a possible keyword
  849.     return false;
  850.   },
  851.   /**
  852.    *
  853.    */
  854.   findByKeyword: function(kwd) {
  855.     return this._ss.getEngineByAlias(kwd);
  856.   },
  857.   /**
  858.    *
  859.    */
  860.   findEngines: function (nameHints) {
  861.     var filteredEngines = [];
  862.     var allEngines = this._engines;
  863.     var self = this;
  864.     nameHints.forEach(function(hint) {
  865.       hint = trim(hint).toLowerCase();
  866.       if(hint.length > 0) {
  867.         var engine = self.findByKeyword(hint);
  868.         if(engine) {
  869.           filteredEngines.push(engine);
  870.         } else {
  871.           allEngines.forEach(function(e){
  872.             var name = e.name.toLowerCase();
  873.             if(name.indexOf(hint) == 0 && filteredEngines.indexOf(e) < 0) {
  874.               filteredEngines.push(e);
  875.             }
  876.           });
  877.         }
  878.       }
  879.     });
  880.     return filteredEngines;
  881.   }
  882. }
  883.  
  884. function trim(str) {
  885.   return str ? str.replace(/^\s*/, '').replace(/\s*$/, '') : '';
  886. }
  887.  
  888. var FILE_SEP = '/';  //Default value.
  889.  
  890. // XXX how do we determine the operating system. we want to set the file
  891. // separator. maybe that the file object has the required property
  892. // get profile directory
  893. (function() {
  894.   try {
  895.     var file = Cc["@mozilla.org/file/local;1"]
  896.                   .createInstance(Ci.nsILocalFile);
  897.     file.initWithPath("c:\\");  //do all windows have a c-drive?
  898.   if(file.exists()) {
  899.     FILE_SEP = "\\";
  900.   }
  901.   }catch(e){
  902.     //log(e);
  903.   }
  904. })();
  905.  
  906. var File = function(x) {
  907.   if(typeof x === "string") {
  908.     var path = x;
  909.     // best possible sub stitution?
  910.     var file = Cc["@mozilla.org/file/local;1"]
  911.                   .createInstance(Ci.nsILocalFile);
  912.     file.initWithPath(path);
  913.     // save the file handle for future reference.
  914.     x = file;
  915.   }
  916.   this.handle = x;
  917.   var path = this.path = x.path;
  918.   this.name = path.substring(path.lastIndexOf(FILE_SEP) + 1);
  919. }
  920.  
  921. /**
  922.  * Object corresponding to an actual file.
  923.  */
  924. File.prototype = {
  925.   exists: function() {
  926.     return this.handle.exists();
  927.   },
  928.   /**
  929.    * returns true if file is a file and not a directory.
  930.    */
  931.   isFile: function() {
  932.     return this.handle.isFile();
  933.   },
  934.   /**
  935.    * returns true if file is a directory
  936.    */
  937.   isDir: function() {
  938.     return this.handle.isDirectory();
  939.   },
  940.   /**
  941.    * returns a list of files available in the current folder.
  942.    * @param filter {string} a simple wildcard filter or a function
  943.    */
  944.   getFiles: function(filter) {
  945.     filter = (typeof filter === "function") ? filter : getWidlcardFilter(filter);
  946.     var arrey = [];
  947.     try {
  948.       var entries = this.handle.directoryEntries;
  949.       while(entries && entries.hasMoreElements()) {
  950.         var entry = entries.getNext();
  951.         
  952.         // can we get the name from entry?
  953.         var file = entry.QueryInterface(Ci.nsIFile);
  954.         var path = file.path;
  955.         var name = path.substring(path.lastIndexOf(FILE_SEP) + 1)
  956.         if(filter(name)) {
  957.           arrey.push(new File(file));
  958.         }
  959.       }
  960.     } catch(e) {}
  961.     return arrey;
  962.   },
  963.   /**
  964.    * appends the name to the file and returns the new file object.
  965.    */
  966.   append: function(name) {
  967.     return this.handle.append(name);
  968.   },
  969.   /**
  970.    * returns the file path representing the file
  971.    */
  972.   getPath: function() {
  973.     return this.handle.path;
  974.   }
  975.   // no file I/O is being provided here, its just for the purpose of getting a
  976.   // list of files in the file system.
  977. }
  978.  
  979. function getWidlcardFilter(simple_wildcard) {
  980.   function any() {
  981.     return true;
  982.   }
  983.   function none() {
  984.     return false;
  985.   }
  986.   function is(aName) {
  987.     return aName === simple_wildcard;
  988.   }
  989.   function prefix(aName) {
  990.     return aName.indexOf(simple_wildcard) === 0;
  991.   }
  992.   // some simple functions for trivial cases
  993.   if(simple_wildcard === undefined) {
  994.     return none;
  995.   }
  996.   if(simple_wildcard.length === 0) {
  997.     return any;
  998.   }
  999.   if(simple_wildcard.indexOf("*") < 0) {
  1000.     return is;
  1001.   }
  1002.   
  1003.   var parts = simple_wildcard.split("*");
  1004.   var first = parts.shift();
  1005.   var len = parts.length;
  1006.   return function(aName) {
  1007.     var part,
  1008.         idxPart,
  1009.         idxFirst = aName.indexOf(first);
  1010.     if(idxFirst !== 0) {
  1011.       return false;
  1012.     }
  1013.     aName = aName.substring(idxFirst + first.length);
  1014.     for(var i = 0; i < len; i++) {
  1015.       part = parts[i];
  1016.       idxPart = aName.indexOf(part);
  1017.       if(idxPart < 0) {
  1018.         return false;
  1019.       }
  1020.       aName = aName.substring(idxPart + part.length);
  1021.     }
  1022.     return true;
  1023.   }
  1024. }
  1025.  
  1026. function getFileCompletions(path, MAX_COUNT) {
  1027.   try {
  1028.     // sanitize path
  1029.     MAX_COUNT = MAX_COUNT || 1000; // any pref?
  1030.     if(path.indexOf("file://") === 0) {
  1031.       path = path.substring("file://".length);
  1032.       // check for windows drive
  1033.       if(path.indexOf(":") === 2 && path.indexOf("/") === 0) {
  1034.         path = path.substring(1);
  1035.       }
  1036.     }
  1037.     path = path.replace(/\//g, FILE_SEP);
  1038.     var lisep = path.lastIndexOf(FILE_SEP);
  1039.     var filter, dirpath;
  1040.     var paths = path.split(FILE_SEP);
  1041.     // get the root file. no filter supported in root name.
  1042.     var rootFile = new File(paths.shift());
  1043.     // take the last path out for preparing final filter
  1044.     var lastPath = paths.pop();
  1045.     
  1046.     var files = [rootFile], t;
  1047.     for(var i = 0; i < paths.length; i++) {
  1048.       var filter = paths[i];
  1049.       t = [];
  1050.       files.forEach(function(f) {
  1051.         if(f.isDir()) {
  1052.           t = t.concat(f.getFiles(filter));
  1053.         }
  1054.       });
  1055.       files = t;
  1056.     }
  1057.     t = [];
  1058.     var lastPathFilter = lastPath + "*";
  1059.     
  1060.     for(var i = 0; i < files.length && t.length < MAX_COUNT; i++) {
  1061.       if(files[i].isDir()) {
  1062.         t = t.concat(files[i].getFiles(lastPathFilter));
  1063.       }
  1064.     }
  1065.     return t;
  1066.   } catch(e) {
  1067.     // ignore
  1068.   }
  1069.   return [];  //t || [];
  1070. }
  1071.  
  1072. var components = [OmnibarSearch, DetailedOmnibarSearch, OmnibarAllInOne, SearchUtils];
  1073.  
  1074. function NSGetModule(compMgr, fileSpec) {
  1075.   return XPCOMUtils.generateModule(components);
  1076. }
  1077.